package com.jeequan.components.http;

import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 证书域名校验
 *
 * @author roy
 */
@Slf4j
public class MyHostnameVerifier implements HostnameVerifier {

    /**
     * 正则：用于从证书信息CN(Common Name)中提取证书域名
     */
    private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)");

    @Override
    public boolean verify(String urlHostName, SSLSession sslSession) {
        log.info("证书域名校验：{}", urlHostName);
        X509Certificate[] x509Certs = null;
        try {
            x509Certs = (X509Certificate[]) sslSession.getPeerCertificates();
        } catch (SSLPeerUnverifiedException e) {
            log.error("证书域名校验异常", e);
            return false;
        }
        List<String> domains = getDomainFromCert(x509Certs[0]);
        if (domains.isEmpty()) {
            // 如果证书无域名，则当作校验失败
            return false;
        }
        return domains.stream().anyMatch(d -> {
            String peerIdentity = d.replace("*.", "");
            // 域名匹配
            return urlHostName.endsWith(peerIdentity);
        });
    }

    /**
     * 从证书获取域名
     *
     * @param x509Certificate
     * @return
     */
    public List<String> getDomainFromCert(X509Certificate x509Certificate) {
        List<String> names = getSubjectAlternativeNames(x509Certificate);
        if (names.isEmpty()) {
            // 企图从Common Name获取域名
            String name = x509Certificate.getSubjectDN().getName();
            Matcher matcher = cnPattern.matcher(name);
            if (matcher.find()) {
                name = matcher.group(2);
            }
            names.add(name);
        }
        return names;
    }

    /**
     * 获取证书AltnativeName里的域名
     *
     * @param certificate
     * @return
     */
    private List<String> getSubjectAlternativeNames(X509Certificate certificate) {
        List<String> identities = new ArrayList<String>();
        try {
            Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
            if (altNames == null) {
                return Collections.emptyList();
            }
            Iterator<List<?>> iterator = altNames.iterator();
            do {
                if (!iterator.hasNext()) {
                    break;
                }
                List<?> altName = iterator.next();
                int size = altName.size();
                if (size >= 2) {
                    identities.add((String) altName.get(1));
                }
            } while (true);
        } catch (CertificateParsingException e) {
            log.error("证书解析异常", e);
        }
        return identities;
    }

}
